package proxy

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"

	"rt.com/mocktool/utils/glog"

	"rt.com/mocktool/utils/vals"

	goproxy "gopkg.in/elazarl/goproxy.v1"
	"rt.com/mocktool/mock"
)

// Proxy 管理服务器类
type Proxy struct {
	// SIGINAL 关闭信号
	SIGINAL chan int
	// Server 代理服务器实例
	Server *http.Server
}

// Instance Mock包实例
var Instance = &Proxy{}

// Start 启动代理服务器
func (proxy *Proxy) Start(op chan int) {
	// 设置信号参数
	proxy.SIGINAL = op
	// 初始化代理服务Handler
	proxyHandler := goproxy.NewProxyHttpServer()
	proxyHandler.Verbose = false
	proxyHandler.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:443$"))).
		HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
			glog.Debug(host)
			return goproxy.OkConnect, host
		})
	proxyHandler.OnRequest().Do(Instance)
	proxyHandler.OnResponse().DoFunc(func(resp *http.Response, ctx *goproxy.ProxyCtx) *http.Response {
		debugResp(resp)
		return resp
	})
	// 生成代理服务本机监听地址
	address := ":" + strconv.Itoa(mock.Instance.GetConfig().ProxyPort)
	// 初始化代理服务器
	proxy.Server = &http.Server{
		Addr:              address,
		Handler:           proxyHandler,
		ReadHeaderTimeout: 15 * time.Second,
		ReadTimeout:       15 * time.Second,
		WriteTimeout:      15 * time.Second,
	}
	// 阻塞开启服务
	proxy.Server.ListenAndServe()
	// 阻塞结束发送2信号量重启服务
	proxy.SIGINAL <- 2
}

// Restart 关闭代理服务器
func (proxy *Proxy) Restart() {
	proxy.Server.Close()
}

// Handle 代理处理
func (proxy *Proxy) Handle(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {
	// 打印API
	debugReq(r)
	// 获得地址
	address := vals.JoinStr(r.URL.Scheme, "://", r.URL.Host, r.URL.Path)
	glog.Debug(address) // 打印地址日志
	// 判断是否开启了全局关闭拦截
	if mock.Instance.GetConfig().GlobalInactive != 0 {
		glog.Debug(address, " E0", mock.Instance.URLMap)
		return r, nil
	}
	// 判断URLMap中是否存在当前地址
	idList, exist := mock.Instance.URLMap[address]
	if !exist {
		// 判断URLMap中是否存在当前地址
		if strings.HasSuffix(address, "/") {
			address = strings.TrimSuffix(address, "/")
		}
		idList, exist = mock.Instance.URLMap[address]
		if !exist {
			glog.Debug(address, " E1", mock.Instance.URLMap)
			return r, nil
		}
	}
	// 地址存在
	for _, id := range idList {
		detail, exist := mock.Instance.MethodList[id]
		if !exist {
			continue
		}
		if !detail.Intercept || detail.TYPE != r.Method {
			// 未开启拦截或者HTTP方法不一致
			continue
		}
		// 规则匹配
		rulePass := true
		if detail.MatchParam {
			// 先检查URL参数匹配
			if detail.ParamRules != nil {
				urlParams := r.URL.Query()
				rulePass = checkParamRules(urlParams, detail.ParamRules)
			}
			// 再检查表单参数匹配
			if rulePass && detail.BodyRules != nil {
				// 读取请求的Body体
				body, err := ioutil.ReadAll(r.Body)
				if err != nil {
					glog.Error(err)
					glog.Debug(address, " E2")
					return r, nil
				}
				r.Body.Close()
				// 恢复body流(否则会丢失数据)
				r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
				// 再次解析body体
				r.ParseForm()
				// 恢复body流(否则会丢失数据)
				r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
				// 开始解析
				rulePass = checkBodyRules(r.PostForm, detail.BodyRules)
			}
		}
		// 规则匹配未通过跳到下一个
		if !rulePass {
			continue
		}
		// 覆盖响应
		var newResp *http.Response
		if detail.CoverResp {
			var respType string
			if detail.OverrideRespType == 0 {
				respType = goproxy.ContentTypeText
			} else {
				respType = goproxy.ContentTypeHtml
			}
			newResp = goproxy.NewResponse(r, respType, http.StatusOK, detail.OverrideRespJSON)
		}
		// 延迟
		if detail.RespTimeout != "" {
			timeout, err := strconv.Atoi(detail.RespTimeout)
			if err == nil && timeout > 0 {
				time.Sleep(time.Duration(timeout) * time.Millisecond)
			}
		}
		glog.Debug(address, " E3")
		// 返回第一个满足上述条件的
		return r, newResp
	}
	glog.Debug(address, " E4")
	return r, nil
}

// checkParamRules 检查地址参数规则是否匹配
func checkParamRules(urlParams url.Values, urlRules []mock.ParamRule) bool {
	glog.Debug(urlParams, urlRules)
	rulePass := true
	for _, rule := range urlRules {
		if !rulePass {
			break
		}
		value := urlParams.Get(rule.Name)
		switch rule.Opt {
		case "=":
			if value != rule.Value {
				rulePass = false
			}
			break
		case "!=":
			if value == rule.Value {
				rulePass = false
			}
			break
		case ">":
			if value <= rule.Value {
				rulePass = false
			}
			break
		case "<":
			if value >= rule.Value {
				rulePass = false
			}
			break
		case ">=":
			if value < rule.Value {
				rulePass = false
			}
			break
		case "<=":
			if value > rule.Value {
				rulePass = false
			}
			break
		}
	}
	return rulePass
}

// checkBodyRules 检查Body参数规则是否匹配
func checkBodyRules(formParams url.Values, bodyRules []mock.ParamRule) bool {
	glog.Debug(formParams, bodyRules)
	rulePass := true
	for _, rule := range bodyRules {
		if !rulePass {
			break
		}
		value := formParams.Get(rule.Name)
		switch rule.Opt {
		case "REGX":
			result, err := regexp.Match(rule.Value, []byte(value))
			glog.Debug("REGX ", rule.Value, " ", value, " ", result)
			if err != nil {
				glog.Error(err)
				rulePass = false
			}
			if !result {
				rulePass = false
			}
			break
		case "JSON":
			var ruleMap map[string]interface{}
			var bodyMap map[string]interface{}
			err := json.Unmarshal([]byte(rule.Value), &ruleMap)
			if err != nil {
				break
			}
			err = json.Unmarshal([]byte(value), &bodyMap)
			if err != nil {
				rulePass = false
				break
			}
			rulePass = compareTwoMap(ruleMap, bodyMap)
			break
		}
	}
	return rulePass
}

// compareTwoMap 递归比较两个Json表
func compareTwoMap(ruleMap, bodyMap map[string]interface{}) bool {
	for rk, rv := range ruleMap {
		bv, ok := bodyMap[rk]
		rvt := reflect.TypeOf(rv)
		bvt := reflect.TypeOf(bv)
		if !ok || rvt != bvt {
			return false
		}
		switch rv.(type) {
		case map[string]interface{}:
			rva := rv.(map[string]interface{})
			bva := bv.(map[string]interface{})
			if !compareTwoMap(rva, bva) {
				return false
			}
			break
		case []interface{}:
			// 数组直接pass掉
			break
		default:
			if bv != rv {
				return false
			}
		}
	}
	return true
}

// debugResp 为输出响应打印日志
func debugResp(resp *http.Response) {
	// 基本信息
	glog.Info("================ Proxy Response-S ================")
	r := resp.Request
	glog.Info(r.Proto, " ", r.Method, " ", r.Host, " ", r.URL)
	// 读取body
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		glog.Error(err.Error())
		return
	}
	glog.Info("------BODY------")
	glog.Info(string(body))
	// 恢复表单数据流（读过的流得回传）
	resp.Body.Close()
	resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
	glog.Info("================ Proxy Response-E ================")
}

// debugAPI 为输入接口打印日志
func debugReq(r *http.Request) {
	// 基本信息
	glog.Info("================ Proxy Request-S ================")
	glog.Info(r.Proto, " ", r.Method, " ", r.Host, " ", r.URL)
	// 地址参数
	urlParams := r.URL.Query()
	if len(urlParams) > 0 {
		glog.Info("------URL PARAM------")
		for i := range urlParams {
			glog.Info(i, "=", urlParams.Get(i))
		}
	}
	// Cookies
	cookies := r.Cookies()
	if len(cookies) > 0 {
		glog.Info("------Cookies------")
		for _, c := range cookies {
			glog.Debug(c.Name + " " + c.Value)
		}
	}
	// 表单
	if r.Method == "POST" {
		// 读取body
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			glog.Error(err.Error())
			return
		}
		glog.Info("------BODY------")
		glog.Info(string(body))
		// 恢复表单数据流（读过的流得回传）
		r.Body.Close()
		r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
		// 解析表单
		err = r.ParseForm()
		if err != nil {
			glog.Error(err.Error())
			return
		}
		if len(r.PostForm) > 0 {
			// 表单参数
			glog.Info("------FORM PARAM------")
			for i := range r.PostForm {
				glog.Info(i, "=", r.PostForm.Get(i))
			}
		}
		// 恢复表单数据流（读过的流得回传）
		r.Body.Close()
		r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
	}
	glog.Info("================ Proxy Request-E ================")
}
